/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package config

import (
	"io"
	"os"
	"encoding/json"
	"path/filepath"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"fmt"
)

var _ fmt.Formatter

type Encoder interface{
	Encode(v interface{}) error
}

type Decoder interface{
	Decode(v interface{}) error
}

type ymlEncoder struct {
	writer io.Writer
}

func (ye *ymlEncoder) Encode(v interface{}) error {
	data, err := yaml.Marshal(v)
	if err != nil {
		return err
	}
	_, err = ye.writer.Write(data)
	return err
}

type ymlDecoder struct {
	reader io.Reader
}

func (yd *ymlDecoder) Decode(v interface{}) error {
	data, err := ioutil.ReadAll(yd.reader)
	if err != nil {
		return err
	}
	return yaml.Unmarshal(data, v)
}

type DecoderProvider func(r io.Reader) Decoder

type EncoderProvider func(w io.Writer) Encoder

type Parser struct {
	Dir string
	Ext string
	EncoderProvider EncoderProvider
	DecoderProvider DecoderProvider
}

func NewJsonParser(dir string) *Parser {
	return &Parser{
		Dir:dir,
		Ext: ".json",
		EncoderProvider: func(w io.Writer) Encoder {
			encoder := json.NewEncoder(w)
			encoder.SetIndent("", "\t")
			return encoder
		},
		DecoderProvider: func(r io.Reader) Decoder {
			return json.NewDecoder(r)
		},
	}
}

func NewYmlParser(dir string) *Parser {
	return &Parser{
		Dir:dir,
		Ext: ".yml",
		EncoderProvider: func(w io.Writer) Encoder {
			return &ymlEncoder{writer: w}
		},
		DecoderProvider: func(r io.Reader) Decoder {
			return &ymlDecoder{reader: r}
		},
	}
}

func (p *Parser) ReadFrom(r io.Reader, schema interface{}) error {
	return p.DecoderProvider(r).Decode(schema)
}

// 读取配置文件
func (p *Parser) Read(file string, schema interface{}) error {
	filename := filepath.Join(p.Dir, file + p.Ext)
	fh, err := os.OpenFile(filename, os.O_RDONLY, 0777)
	if err != nil {
		return err
	}
	defer fh.Close()
	return p.ReadFrom(fh, schema)
}

func (p *Parser) CreateTo(schema interface{}, w io.Writer) error {
	return p.EncoderProvider(w).Encode(schema)
}

func (p *Parser) create(file string, schema interface{}, cover bool) (err error) {
	filename := filepath.Join(p.Dir, file + p.Ext)
	dir := filepath.Dir(filename)
	var fh *os.File
	if cover {
		if dir != "" {
			err = os.MkdirAll(dir, 0777)
			if err != nil {
				return err
			}
		}
		fh, err = os.Create(filename)
		if err != nil {
			return err
		}
	} else {
		// create file is not exist
		fh, err = os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0777)
		if os.IsExist(err) {
			return nil
		}
		if err != nil {
			return err
		}
	}
	defer func() {
		if fh != nil {
			fh.Close()
		}
	}()
	return p.CreateTo(schema, fh)
}

func (p *Parser) Backup(file string) error {
	oldFile := filepath.Join(p.Dir, file + p.Ext)
	newFile := filepath.Join(p.Dir, file + "_backup" + p.Ext)
	return os.Rename(oldFile, newFile)
}

func (p *Parser) Create(file string, schema interface{}) error {
	return p.create(file, schema, true)
}

func (p *Parser) CreateIfNotExist(file string, schema interface{}) error {
	return p.create(file, schema, false)
}

func (p *Parser) ReadOrCreate(file string, schema interface{}) error {
	err := p.Read(file, schema)
	if err == nil {
		return nil
	} else if os.IsNotExist(err) {
		return p.Create(file, schema)
	} else {
		return err
	}
}
